package com.cty.ioc.senior;

import com.cty.ioc.senior.definition.BeanDefinition;
import com.cty.ioc.senior.definition.PropertyValue;
import com.cty.ioc.senior.definition.RuntimeBeanReference;
import com.cty.ioc.senior.definition.TypedStringValue;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 高级工程师
 * bean 对象实例
 * beanDefinition 对象定义对象
 *
 * @author : cty
 * @since 2023/11/11
 */


public class SeniorEngineer {

    // 管理从配置文件中读取的beanDefinition
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    // 单例管理创建的bean
    private Map<String, Object> singletonBeanMap = new HashMap<>();

    public SeniorEngineer() {
        this.init();
    }

    /**
     * 0 初始化
     */
    private void init() {
        // 0.1 指定加载路径
        String beanConfigLocation = "bean.xml";
        // 0.2 从类加载路径加载指定路径的配置文件
        InputStream inputStream = this.getResource(beanConfigLocation);
        // 0.3 将数据流转换为格式化数据Docmuent
        Document document = this.createDocument(inputStream);
        // 0.4 按照自定义语义解析Docmuent数据，并存储到beanDefinitionMap
        this.parseBeanDefinitions(document.getRootElement());
    }

    /**
     * 0.2 从类加载路径加载指定路径的配置文件
     *
     * @param beanConfigLocation 类加载路径文件
     * @return 文件流
     */
    private InputStream getResource(String beanConfigLocation) {
        return this.getClass().getClassLoader().getResourceAsStream(beanConfigLocation);
    }

    /**
     * 0.3 将数据流转换为格式化数据Docmuent
     *
     * @param inputStream 文件数据流
     * @return 格式化数据Docmuent
     */
    private Document createDocument(InputStream inputStream) {
        SAXReader reader = new SAXReader();
        try {
            return reader.read(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 0.4 按照自定义语义解析Docmuent数据，并存储到beanDefinitionMap
     *
     * @param rootElement 根元素
     */
    private void parseBeanDefinitions(Element rootElement) {
        List<Element> elementList = rootElement.elements("bean");
        for (Element element : elementList) {
            this.parseBeanDefinition(element);
        }
    }

    /**
     * 0.4 解析单个元素
     *
     * @param beanElement bean元素
     */
    private void parseBeanDefinition(Element beanElement) {
        // 类全路径名称
        String className = beanElement.attributeValue("class");
        if (className == null || className.equals("")) {
            return;
        }
        Class classType = this.resolveClassName(className);
        // bean唯一标识
        String beanName = beanElement.attributeValue("id");
        if (beanName == null || beanName.equals("")) {
            beanName = classType.getSimpleName();
        }
        // bean管理方式，单例或原型
        String scope = beanElement.attributeValue("scope");
        scope = (scope == null || scope.equals("")) ? "singleton" : scope;
        // 初始化方法
        String initMethod = beanElement.attributeValue("init-method");

        // 创建BeanDefinition
        BeanDefinition beanDefinition = new BeanDefinition();
        beanDefinition.setId(beanName);
        beanDefinition.setClassName(className);
        beanDefinition.setClassType(classType);
        beanDefinition.setScope(scope);
        beanDefinition.setInitMethod(initMethod);

        // 解析属性
        List<Element> propertyElementList = beanElement.elements("property");
        for (Element propertyElement : propertyElementList) {
            // 解析属性标签
            this.parsePropertyElement(propertyElement, beanDefinition);
        }
        beanDefinitionMap.put(beanName, beanDefinition);
    }

    /**
     * 0.4.1 根据类全路径名称加载类对象
     *
     * @param className 类全路径名称
     * @return 类对象
     */
    private Class resolveClassName(String className) {
        try {
            return Class.forName(className);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 0.4.2 解析属性标签
     *
     * @param propertyElement 属性元素
     * @param beanDefinition Bean定义对象
     */
    private void parsePropertyElement(Element propertyElement, BeanDefinition beanDefinition) {
        String name = propertyElement.attributeValue("name");
        String value = propertyElement.attributeValue("value");
        String ref = propertyElement.attributeValue("ref");
        if (name == null || name.equals("")) {
            return;
        }

        PropertyValue propertyValue = new PropertyValue();
        propertyValue.setName(name);
        if (value != null && !value.equals("")) {
            TypedStringValue typedStringValue = new TypedStringValue();
            typedStringValue.setValue(value);
            typedStringValue.setClassType(this.resolvePropertyType(beanDefinition.getClassType(), name));
            propertyValue.setValue(typedStringValue);
        } else if (ref != null || !ref.equals("")) {
            RuntimeBeanReference runtimeBeanReference = new RuntimeBeanReference(ref);
            propertyValue.setValue(runtimeBeanReference);
        }
        beanDefinition.addPropertyValue(propertyValue);
    }

    /**
     * 0.4.2 根据类对象获取指定属性名称的类对象
     *
     * @param classType 类对象
     * @param fieldName 属性名称
     * @return 属性类对象
     */
    private Class resolvePropertyType(Class classType, String fieldName) {
        try {
            Field field = classType.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field.getType();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 创建Bean
     *
     * @param userName Bean名称
     * @return Bean
     */
    public Object getBean(String userName) {
        // 1 先从singletonBeanMap查询是否存在对应的bean，若存在直接返回
        if (singletonBeanMap.containsKey(userName)) {
            return singletonBeanMap.get(userName);
        }
        // 2 查看配置文件中是否存在对应的bean，若不存在直接返回null
        if (!beanDefinitionMap.containsKey(userName)) {
            return null;
        }
        // 3 根据beanDefinition创建对应的bean
        BeanDefinition beanDefinition = beanDefinitionMap.get(userName);
        Object bean = this.createBean(beanDefinition);
        // 4 若该bean是单例管理方式，则需要放到singletonBeanMap
        if (beanDefinition.isSingleton()) {
            singletonBeanMap.put(userName, bean);
        }
        // 5 返回bean
        return bean;
    }

    /**
     * 3.1 根据beanDefinition创建对应的bean
     *
     * @param beanDefinition 对象定义对象
     * @return Bean
     */
    private Object createBean(BeanDefinition beanDefinition) {
        // 3.1.1 根据beanDefinition利用反射创建出bean
        Object bean = this.createInstance(beanDefinition);
        // 3.1.2 利用反射对bean进行属性赋值
        this.populateBean(bean, beanDefinition);
        // 3.1.3 利用反射调用对象的初始化方法
        this.initializeBean(bean, beanDefinition);
        return bean;
    }

    /**
     * 3.1.3 利用反射调用对象的初始化方法
     *
     * @param bean 对象
     * @param beanDefinition 对象定义对象
     */
    private void initializeBean(Object bean, BeanDefinition beanDefinition) {
        String initMethod = beanDefinition.getInitMethod();
        if (initMethod == null || initMethod.equals("")) {
            return;
        }
        this.invokeMethod(beanDefinition.getClassType(), bean, initMethod);
    }

    /**
     * 3.1.3 反射执行无参方法
     *
     * @param classType 类对象
     * @param bean 对象
     * @param initMethod 方法名
     */
    private void invokeMethod(Class classType, Object bean, String initMethod) {
        try {
            Method method = classType.getMethod(initMethod);
            method.invoke(bean);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 3.1.1 根据beanDefinition利用反射创建出bean
     *
     * @param beanDefinition 类定义对象
     * @return 对象实例
     */
    private Object createInstance(BeanDefinition beanDefinition) {
        return newInstance(beanDefinition.getClassType());
    }

    /**
     * 3.1.1.1 反射创建bean
     *
     * @param classType 类对象
     * @return 对象实例
     */
    private Object newInstance(Class classType) {
        try {
            Constructor constructor = classType.getConstructor();
            // 反射创建对象，要求类至少有无参构造器！！！
            return constructor.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 3.1.2 利用反射对bean进行属性赋值
     *
     * @param bean 对象实例
     * @param beanDefinition 对象定义对象
     */
    private void populateBean(Object bean, BeanDefinition beanDefinition) {
        List<PropertyValue> propertyValueList = beanDefinition.getPropertyValueList();
        for (PropertyValue propertyValue : propertyValueList) {
            // 3.1.2.1 获取属性名
            String propertyName = propertyValue.getName();
            // 3.1.2.2 获取属性值
            Object value = propertyValue.getValue();
            Object valueToUse = this.parseValue(value);
            // 3.1.2.3 依赖注入
            this.setProperty(beanDefinition.getClassType(), bean, propertyName, valueToUse);
        }

    }

    /**
     * 3.1.2.2 解析属性值
     *
     * @param value 属性值
     * @return 属性值对象
     */
    private Object parseValue(Object value) {
        if (value instanceof TypedStringValue) {
            TypedStringValue typedStringValue = (TypedStringValue) value;
            Class classType = typedStringValue.getClassType();
            String stringValue = typedStringValue.getValue();
            return this.handleType(classType, stringValue);
        } else if (value instanceof RuntimeBeanReference) {
            RuntimeBeanReference runtimeBeanReference = (RuntimeBeanReference) value;
            String ref = runtimeBeanReference.getRef();
            // 注意此处容易出现循环依赖！
            return getBean(ref);
        }
        return null;
    }

    /**
     * 3.1.2.2 解析不同普通类型的属性值
     *
     * @param classType 类对象
     * @param stringValue 字符串值
     * @return 属性值对象
     */
    private Object handleType(Class classType, String stringValue) {
        if (classType == String.class) {
            return stringValue;
        } else if (classType == Integer.class) {
            return Integer.parseInt(stringValue);
        } else if (classType == BigDecimal.class) {
            // other
        }
        return null;
    }

    /**
     * 3.1.2.3 依赖注入
     *
     * @param clasType 类对象
     * @param bean 对象
     * @param propertyName 属性名
     * @param value 属性值
     */
    private void setProperty(Class clasType, Object bean, String propertyName, Object value) {
        try {
            Field field = clasType.getDeclaredField(propertyName);
            field.setAccessible(true);
            field.set(bean, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
